package middle

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/websocket"
	"k8s-platform/config"
	"k8s-platform/service"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/remotecommand"
	"log"
	"net/http"
	"time"
)

var Terminal terminal

type terminal struct{}




//TerminalMessage定义了终端和容器she11交互内容的格式
//operation是操作类型
//Data是具体数据内容
//Rows和cols可以理解为终端的行数和列数，也就是宽、高
type TerminalMessage struct {
	Operation string `json:"operation"`
	Data      string `json:"data"`
	Rows      uint16 `json:"rows"`
	Cols      uint16 `json:"cols"`
}

//交互的结构体  接管输入和输出
type TerminalSession struct {
	wsConn   *websocket.Conn
	sizeChan chan remotecommand.TerminalSize
	doneChan chan struct{}
}

//初始化一个websocket.Upgrader对象 用于http协议升级为ws协议
var upgrader = func() websocket.Upgrader {
	upgrader := websocket.Upgrader{}
	upgrader.HandshakeTimeout = time.Second * 2
	upgrader.CheckOrigin = func(r *http.Request) bool {
		return true
	}
	return upgrader
}()

//创建TerminalSession类型的对象并返回
func NewTerminalSession(w http.ResponseWriter, r *http.Request) (*TerminalSession, error) {
	//升级ws协议
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return nil, errors.New("升级websocket失败:" + err.Error())
	}

	return &TerminalSession{
		wsConn:   conn,
		sizeChan: make(chan remotecommand.TerminalSize),
		doneChan: make(chan struct{}),
	}, nil
}

//读数据的方法 返回值int是读成功了多少数据
func (t *TerminalSession) Read(p []byte) (int, error) {
	_, message, err := t.wsConn.ReadMessage()
	if err != nil {
		log.Printf("read message err:%v\n", err)
		return 0, err
	}
	//反序列化
	var msg TerminalMessage
	if err = json.Unmarshal(message, &msg); err != nil {
		log.Printf("read parse message err:%v\n", err)
		return 0, err
	}
	//逻辑判断
	switch msg.Operation {
	case "stdin":
		return copy(p, msg.Data), nil
	case "resize":
		t.sizeChan <- remotecommand.TerminalSize{
			Width:  msg.Cols,
			Height: msg.Rows,
		}
		return 0, nil
	case "ping":
		return 0, nil
	default:
		log.Printf("unknown message type '%s'\n", msg.Operation)
		return 0, fmt.Errorf("unknown message type '%s'", msg.Operation)
	}
}

//写数据的方法 拿到挨批server的返回内容 像web端输出
func (t *TerminalSession) Write(p []byte) (int, error) {
	msg, err := json.Marshal(TerminalMessage{
		Operation: "stdout",
		Data:      string(p),
	})
	if err != nil {
		log.Printf("json parse message err: %v\n", err)
		return 0, err
	}
	if err = t.wsConn.WriteMessage(websocket.TextMessage, msg); err != nil {
		log.Printf("write message err: %v\n", err)
		return 0, err
	}
	return len(p), nil
}

//标记关闭的方法
func (t *TerminalSession) Done()  {
	close(t.doneChan)
}

//关闭的方法
func (t *TerminalSession) Close() error {
	//t.wsConn.Close()
	close(t.doneChan)
	return t.wsConn.Close()
}

//resize方法 以及是否退出终端
func (t *TerminalSession) Next() *remotecommand.TerminalSize {
	select {
	case size := <- t.sizeChan:
		return &size
	case <- t.doneChan:
		return nil
	}
}

//wshandler
func (t *terminal) WsHandler(w http.ResponseWriter, r *http.Request) {
	//解析form入参 ，获取namespace pod container参数
	if err := r.ParseForm(); err != nil {
		log.Printf("参数解析失败" + err.Error())
		return
	}
	namespace := r.Form.Get("namespace")
	podName := r.Form.Get("pod_name")
	containerName := r.Form.Get("container_name")
	log.Printf("exec pod:[%s],container:[%s] ,namespace:[%s] \n", podName, containerName, namespace)

	//加载k8s配置
	cfg, err := clientcmd.BuildConfigFromFlags("", config.Kubeconf)
	if err != nil {
		log.Printf("加载k8s配置失败" + err.Error())
		return
	}

	option := &corev1.PodExecOptions{
		Command:   []string{"bash"},
		Stdin:     true,
		Stdout:    true,
		Stderr:    true,
		TTY:       true,
		Container: containerName,
	}

	//组装post请求
	req := service.K8s.ClientSet.CoreV1().RESTClient().Post().
		Resource("pods").
		Name(podName).
		Namespace(namespace).
		SubResource("exec").
		VersionedParams(option, scheme.ParameterCodec)

	log.Printf("exec post request url: %v", req.URL())
	//升级SPDY协议
	executor, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL())
	if err != nil {
		log.Println("建立SPDY连接失败", err)
		return
	}

	pty, err := NewTerminalSession(w, r)
	if err != nil {
		log.Printf("实例化TerminalSession失败" + err.Error())
		return
	}

	//处理关闭
	defer func() {
		log.Printf("关闭TerminalSession")
		pty.Close()
	}()

	//与kubelet 建立stream连接
	err = executor.Stream(remotecommand.StreamOptions{
		Stdin:             pty,
		Stdout:            pty,
		Stderr:            pty,
		TerminalSizeQueue: pty,
		Tty:               true,
	})

	if err != nil {
		log.Println("执行pod命令失败：", err)
		//将报错返回给web端
		pty.Write([]byte("执行pod命令失败：" + err.Error()))
		//标记关闭
		//pty.Done()
		return
	}
}